﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using FunctionalCLR.Core;
using FunctionalCLR.Shell;
using Zove.Compiler;
using System.Configuration;

namespace Zove.Runtime
{
    /// <summary>
    /// The default class loader for the framework
    /// </summary>
    public class DefaultClassLoader : IClassLoader
    {
        /// <summary>
        /// The function manager
        /// </summary>
        public IFunctionManager FunctionManager
        {
            get;
            set;
        }

        /// <summary>
        /// The parser factory
        /// </summary>
        public IParserFactory ParserFactory
        {
            get;
            set;
        }

        /// <summary>
        /// The class cache
        /// </summary>
        protected Dictionary<string, ZoomClass> ClassCache
        {
            get;
            set;
        }

        /// <summary>
        /// Read the source from a file
        /// </summary>
        /// <param name="fileName">the file name</param>
        /// <returns></returns>
        public static string LoadSource(string fileName)
        {
            FileStream fs = new FileStream(fileName, FileMode.Open);
            StreamReader reader = new StreamReader(fs);

            string source = reader.ReadToEnd();

            reader.Close();

            return source;
        }

        /// <summary>
        /// The class finder
        /// </summary>
        public IClassFinder Finder
        {
            get;
            set;
        }

        /// <summary>
        /// The default constructor
        /// </summary>
        public DefaultClassLoader()
        {
            this.ClassCache = new Dictionary<string, ZoomClass>();
        }

        #region IClassLoad Members
        
        /// <summary>
        /// Loading a class
        /// </summary>
        /// <param name="className">the name of the class</param>
        /// <returns>the zoom class</returns>
        public ZoomClass Load(string className)
        {
            string fileName = Finder.GetLocation(className);
            if (string.IsNullOrEmpty(fileName))
            {
                return null;
            }

            return LoadFromUri(fileName);
        }

        /// <summary>
        /// Check if performance optimised tag is forced in the configuration
        /// </summary>
        public bool PerformanceOptimised
        {
            get
            {
                object value = ConfigurationManager.AppSettings["performance"];
                if (value != null && value == "true")
                {
                    return true;
                }

                return false;
            }
        }

        /// <summary>
        /// Loading a class from a 
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        public ZoomClass LoadFromUri(string uri)
        {
            if (PerformanceOptimised)
            {
                if (ClassCache.ContainsKey(uri))
                {
                    return ClassCache[uri];
                }
            }

            string source = DefaultClassLoader.LoadSource(uri);
            ZoomClass zc = this.LoadFromSource(source);
            zc.Url = uri;
            if (PerformanceOptimised)
            {
                ClassCache.Add(uri, zc);
            }

            return zc;
        }

        /// <summary>
        /// Load class from its source
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public ZoomClass LoadFromSource(string source)
        {
            Lexer lexer = new Lexer(source);

            ZoomClass c = new ZoomClass();
            while (lexer.HasMoreTokens())
            {
                Token token = lexer.NextToken("class", "import");
                if (token.Value == "import")
                {
                    token = lexer.NextToken();
                    StringBuilder b = new StringBuilder();
                    while (lexer.HasMoreTokens()
                        && token.TokenType != TokenType.Semicolon)
                    {
                        b.Append(token.Value);
                        token = lexer.NextToken();
                    }
                    if (!lexer.HasMoreTokens())
                    {
                        throw new UnexpectedEndOfFileException();
                    }

                    string path = b.ToString();
                    if (!Directory.Exists(path))
                    {
                        path = Environment.CurrentDirectory + "\\" + path;
                        if (!Directory.Exists(path))
                        {
                            throw new DirectoryNotFoundException(
                                token.Value);
                        }
                    }
                    ZoveDependencyManager.Instance.ClassFinder.AddWorkspace(
                        path);
                }
                else if (token.Value == "class")
                {
                    # region class definition
                    c.Name = lexer.NextToken().Value;
                    c.Summary = lexer.LastComment;

                    token = lexer.NextToken("{", "extends");
                    if (token.Value.Equals("extends"))
                    {
                        do
                        {
                            token = lexer.NextToken(TokenType.Identifier);
                            c.BaseClasses.Add(token.Value);
                            token = lexer.NextToken();
                        } while (token.TokenType != TokenType.LeftBracket);
                    }

                    token = lexer.PeekToken();
                    # region class internal
                    while (token.Value != "}")
                    {
                        Token nt = lexer.PeekToken(2);
                        if (token.TokenType == TokenType.Identifier
                            && nt.TokenType == TokenType.Identifier)
                        {
                            token = lexer.NextToken();
                            string type = token.Value;

                            // Syntax:
                            // factor volume \m3, area \m2;
                            // factor A \radian, B \radian, C \radian;
                            // A = 60 \degree; # convert 60 degrees to PI/3 automatically
                            do
                            {
                                token = lexer.NextToken();
                                if (token.TokenType == TokenType.Semicolon)
                                {
                                    break;
                                }
                                else if (token.TokenType == TokenType.Identifier)
                                {
                                    if (type == "const")
                                    {
                                        string constantName = token.Value;
                                        lexer.NextToken(TokenType.Assign);
                                        token = lexer.NextToken();

                                        ZoomConstant constant = new ZoomConstant();
                                        constant.Name = constantName;
                                        constant.Value = token.Value;
                                        c.Constants.Add(constant);
                                    }
                                    else
                                    {
                                        ZoomVariable v = new ZoomVariable();
                                        v.Parent = c;
                                        v.Name = token.Value;
                                        v.Type = type;
                                        v.Summary = lexer.LastComment;
                                        c.Variables.Add(v);

                                        token = lexer.NextToken();
                                        if (token.TokenType == TokenType.ForwardSlash)
                                        {
                                            if (v.Type != "factor")
                                            {
                                                throw new Exception("Only factor can declare with unit of measurement.");
                                            }

                                            // it is a unit
                                            token = lexer.NextToken(TokenType.Identifier);
                                            string unit = token.Value;
                                            v.Unit = unit;
                                        }
                                        else if (token.TokenType == TokenType.Comma)
                                        {
                                        }
                                        else if (token.TokenType == TokenType.Semicolon)
                                        {
                                            break;
                                        }
                                    }
                                }
                            }
                            while (lexer.HasMoreTokens());
                        }
                        else
                        {
                            int offset = lexer.Index;
                            string ln = lexer.ReadUntil(';');
                            if (ln.Contains(":="))
                            {
                                // this is a function definition

                                IUserDefinedType udf = AddUserDefinedObject(
                                    ln, c.Name, lexer.LineNumber, lexer.LastComment,
                                    new DefaultUserDefinedFunctionFactory());
                                //} else if (ln.Contains("->")) {
                                //    // it is an inline function / rule declaration
                                //    // get to the end of the line

                                //    lexer.Index = offset;
                                //    while (string.IsNullOrEmpty(
                                //        (ln = lexer.NextLine()).Trim()) || ln.StartsWith("#")) {
                                //    }
                                //    ln = ln.Replace("->", ":=");
                                //    this.AddUserDefinedObject(
                                //        ln, c.Name, lexer.LineNumber,
                                //        new DefaultUserDefinedRuleFactory());
                            }
                            else
                            {
                                string[] p = ln.Split('=');

                                //for (int i = 0; i < p.Length; i++) {
                                //    p[i] = p[i].Trim();
                                //    p[i] = ApplyRulesInStatement(c.Name, p[i]);
                                //}

                                #region analysis
                                for (int i = 0; i < p.Length; i++)
                                {
                                    List<string> id = Utils.GetAllIdentifiers(p[i].Trim());

                                    for (int j = i + 1; j < p.Length; j++)
                                    {
                                        List<string> right = Utils.GetAllIdentifiers(p[j].Trim());
                                        right.AddRange(id);

                                        ZoomEquation e = new ZoomEquation();
                                        e.LineNumber = lexer.LineNumber;
                                        e.Parent = c;
                                        e.Left = p[i].Trim();
                                        e.Right = p[j].Trim();
                                        e.Equation = e.Left + "=" + e.Right;

                                        foreach (string m in right)
                                        {
                                            object constant = GetConstant(m);
                                            if (constant == null)
                                            {
                                                if (!DefaultClassLoader.HasElement(m, e))
                                                {
                                                    ZoomElement element = new ZoomElement();
                                                    element.Name = m;

                                                    e.Elements.Add(element);
                                                }
                                            }
                                            else
                                            {
                                                e.Equation = Utils.ReplaceString(
                                                    m, constant.ToString(), e.Equation);
                                            }
                                        }
                                        c.Equations.Add(e);
                                    }
                                }
                                #endregion
                            }
                        }

                        token = lexer.PeekToken();
                    }
                    lexer.NextToken(); // should be '}'
                    #endregion
                    #endregion
                }
            }

            return c;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="constantName"></param>
        /// <returns></returns>
        public object GetConstant(string constantName)
        {
            if (constantName.IndexOf('.') < 0)
            {
                return null;
            }

            int index = constantName.IndexOf('.');
            string var = constantName.Substring(0, index);
            string constant = constantName.Substring(index + 1, constantName.Length - index - 1);
            ZoomClass zc = ZoveDependencyManager.Instance.ClassLoader.Load(var);
            if (zc != null)
            {
                ZoomConstant c = zc.Constants.GetConstant(constant);
                if (c != null)
                {
                    return c.Value;
                }
            }

            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="className"></param>
        /// <param name="expr"></param>
        /// <returns></returns>
        public string ApplyRulesInStatement(
            string className, string expr)
        {
            UserDefinedFunctionConverter converter
                = new UserDefinedFunctionConverter(
                    className, ReplacingMode.StartingMatch);
            // if the function is declared in its class
            // place the class name in the front of the function call
            // to identify it
            expr = Utils.PrefixizeFunction(className, expr,
                ZoveDependencyManager.Instance.FunctionManager);
            Parser p
                = DefaultDependencyManager.Instance.ParserFactory.CreateParser(
                expr);
            p.FunctionManager = ZoveDependencyManager.Instance.FunctionManager;
            Node root = p.Parse();
            expr = converter.Convert(root);

            return expr;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ln"></param>
        /// <param name="className"></param>
        /// <param name="lineNumber"></param>
        /// <param name="comment"></param>
        /// <param name="udfFactory"></param>
        /// <returns></returns>
        public IUserDefinedType AddUserDefinedObject(
            string ln, string className, int lineNumber, string comment,
            IUserDefinedFunctionFactory udfFactory)
        {
            UserDefinedFunctionParser udfp
                            = new UserDefinedFunctionParser(
                                this.ParserFactory, udfFactory);
            IUserDefinedType udf = udfp.Parse(ln);
            udf.Name = className + "." + udf.Name;
            udf.ClassName = className;
            udf.LineNumber = lineNumber;
            udf.ParserFactory = this.ParserFactory;
            udf.Comment = comment;
            udf.FunctionManager = this.FunctionManager;
            ZoveDependencyManager.Instance.FunctionManager.Add(udf.Name, udf);

            return udf;
        }

        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="equ"></param>
        /// <returns></returns>
        public static bool HasElement(string name, ZoomEquation equ)
        {
            foreach (ZoomElement e in equ.Elements)
            {
                if (e.Name == name)
                {
                    return true;
                }
            }

            return false;
        }
    }
}
